package solvers.linearsupport;

import globals.CentralStatics;

import java.text.DecimalFormat;
import java.util.ArrayList;

import org.apache.commons.math3.linear.*;

import value.ValueAtBeliefVector;


public class CornerPoint<T> implements Comparable<CornerPoint<T>>{
	public double[] weightVector;
	public ArrayList<ValueVector<T>> valueVectors;
	public int[] searchWeightsIndicesVVs;
	public ArrayList<Integer> zeroWeights; //for boundry corner points
	public double maxError = 0;
	public double value;
	public double maxOptimisticValue;
	public boolean valid = true;
	
	public static int classCNT = 0;
	//boolean active;
	
	public CornerPoint(double[] w, ArrayList<ValueVector<T>> valueVs, int[] swi, ArrayList<Integer> zeroWs){
		this.weightVector = w;
		this.valueVectors = valueVs;
		this.zeroWeights = zeroWs;
		this.maxError = 0;
		this.value = CentralStatics.innerProduct(valueVs.get(0).p.getValue(), w);
		this.maxOptimisticValue = 0;
		this.searchWeightsIndicesVVs = swi;
	}
	
	public CornerPoint(ArrayList<ValueVector<T>> valueVs, int[] swi,  ArrayList<Integer> zeroWs){
		this.weightVector = null;
		this.maxError = 0;
		this.valueVectors = valueVs;
		this.zeroWeights = zeroWs;
		this.value=Double.MIN_VALUE;
		this.searchWeightsIndicesVVs = swi;
		this.maxOptimisticValue =0; 
	}
	
	@Override
	public int compareTo(CornerPoint<T> cp) {
		if(this.maxError==cp.maxError){
			return 0;
		} else if (this.maxError>cp.maxError){
			return 1;
		} else {
			return -1;
		}
	}
	
	public String toString(){
		String result="";
		result+="---------------------------\nMaxErr:"+this.maxError+"\nZeroWeights:";
		for(Integer i : this.zeroWeights){
			result+=" "+i.intValue();
		}
		result+="\nValue vectors:";
		for(ValueVector<T> vv : this.valueVectors){
			result+=" "+vv.p.toString()+vv.toString()+" ||";
		}
		result+="\nWeight vector: (";
		DecimalFormat df = new DecimalFormat("#.##");
		for(int i=0; i<this.weightVector.length;i++){
			result+=df.format(this.weightVector[i]);
			if(i!=this.weightVector.length-1){
				result+=",";
			} else {
				result+=")";
			}
		}
		result+="\nValue: "+this.value;
		//result+="\n";
		result+="\n---------------------------";
		return result;
	}
	
	public void calcWnE2D(){
		if(this.valueVectors.size()!=2){
			System.err.println("CornerPoint 2D error: "+this.valueVectors.size()+" value vectors");
			System.exit(21);
		} 
		ValueVector<T> v0 = this.valueVectors.get(0);
		ValueVector<T> v1 = this.valueVectors.get(1);
		double[] ab = v0.p.getValue();
		double[] cd = v1.p.getValue();
		//ax + b(1-x) = cx + d(1-x) = (a-b)x+b = (c-d)x + d
		//(a+d-b-c)x = d-b
		//x = (d-b) / ((a-c)+(d-b))
		double d_b = cd[1]-ab[1];
		double w0 = d_b / (ab[0]-cd[0]+d_b);
		this.weightVector = new double[2];
		this.weightVector[0]=w0;
		this.weightVector[1]=1-w0;
		this.value = ab[0]*w0+ab[1]*(1-w0);
		
		if(w0==Double.NaN){
			System.err.println("a:"+ab[0]+" b:"+ab[1]);
			System.err.println("c:"+ab[0]+" d:"+ab[1]);
		}
		
		//Now calculate Delta_max and then epsilon_max
		double wv0 = v0.getSearchWeights().get(0)[0];
		double wv1 = v1.getSearchWeights().get(0)[0];
		double interval = Math.abs(wv0-wv1);
		double frac0 = Math.abs(w0-wv1)/interval;
		//System.out.println("#@ w:("+this.weightVector[0]+ ", "+this.weightVector[1]+")");
		//System.out.println("#@ "+frac0+ " $ "+v0.valueAtSearchWeights[0]+"@"+wv0+"|"+v1.valueAtSearchWeights[0]+"@"+wv1);
		this.maxOptimisticValue = frac0*v0.valueAtSearchWeights[0];
		this.maxOptimisticValue += (1-frac0)*v1.valueAtSearchWeights[0];
		
		this.maxError = (this.maxOptimisticValue-this.value)/this.maxOptimisticValue;
		//System.out.println("#@ "+value+"@"+w0+ " : "+ maxOptimisticValue+"|"+maxError);
		//this.valueVectors.get(0).searchWeights.get(0);
	}
	
	public void calcWeightsAndValue(){
		int dim = this.valueVectors.get(0).p.length();
		int dim_ = dim - this.zeroWeights.size();
		double[][] mat = new double[dim_+1][dim_+1];
		double[] solveVec = new double[dim_+1];
		
		for(int i=0; i<this.valueVectors.size(); i++){
			double[] vec = this.valueVectors.get(i).p.getValue();
			int correct =0;
			for(int j=0; j<vec.length; j++){
				if(this.zeroWeights.contains(new Integer(j))){
					correct++;
				} else {
					mat[i][j-correct] = vec[j];
				}
			}
			mat[i][dim_] = -1;
			solveVec[i]=0;
		}

		for(int j=0; j<dim_;j++){
			mat[dim_][j] = 1;
		}
		mat[dim_][dim_]=0;
		solveVec[dim_]=1;
		
		RealMatrix coefficients = new Array2DRowRealMatrix(mat,false);
		//coefficients.transpose();
		//if(++classCNT==3){
		try{
			DecompositionSolver solver = new LUDecomposition(coefficients).getSolver();			          
			RealVector constants = new ArrayRealVector(solveVec, false);
			RealVector solution = solver.solve(constants);
			double[] arr=solution.toArray();
			this.value=arr[arr.length-1];
			this.weightVector = new double[dim];
			int correct =0;
			for(int j=0; j<this.weightVector.length; j++){
				if(this.zeroWeights.contains(new Integer(j))){
					this.weightVector[j] = 0;
					correct++;
				} else {
					this.weightVector[j] = arr[j-correct];
				}
				if(this.weightVector[j]<0){
					this.valid=false;
				}
				if(this.weightVector[j]>1){
					this.valid=false;
				}
			}	
		}catch(SingularMatrixException sme){
			this.valid = false;
			//System.err.println("NYAA: "+sme.getMessage());
		}
		
	}
	
	//public void calcAll2DOptimized(){
	//}
	
	public void determineMaxError(){
		int dim = this.valueVectors.get(0).getSearchWeights().get(0).length;
		int dim_ = dim - this.zeroWeights.size();
		double[][] mat = new double[dim_][dim_];
		//double[] solveVec = new double[dim_];
		double[] vecVals = new double[dim_];
		for(int i=0; i<this.valueVectors.size(); i++){
			double[] vec = this.valueVectors.get(i).getSearchWeights().get(this.searchWeightsIndicesVVs[i]);
			vecVals[i] = CentralStatics.innerProduct(vec,this.valueVectors.get(i).p.getValue());
			int correct =0; //TODO VANAF HIER
			for(int j=0; j<vec.length; j++){
				if(this.zeroWeights.contains(new Integer(j))){
					correct++;
				} else {
					if(j-correct==vec.length-1){
						mat[i][j-correct] = 1;
					} else {
						mat[i][j-correct] = vec[j];
					}
				}
			}
			//solveVec[i]=this.weightVector[i];
		}
		
		LinearFunction lf = new LinearFunction();
		boolean rs = lf.inferFromPoints(mat,vecVals);
		double[] inputVec = new double[dim_];
		int correct =0;
		for(int i=0; i<this.weightVector.length-1; i++){
			if(this.zeroWeights.contains(new Integer(i))){
				correct++;
			} else {
				inputVec[i-correct] = this.weightVector[i];
			}
		}
		inputVec[dim_-1] = 1;
		if(!rs){
			//System.out.print("!");
			this.maxError = 1;//(searchWeight_0..dim, valueAtSearchWeights) are
			// linearly dependent. Should find other value vectors and searchWeights
			//to make a bounding box around this corner weight, but this is difficult
			// this is a hack, that increases the number of weights for which
			// findBest is called, i.e., it makes the algorithm slower. 
			//TODO: maybe fix later.
		} else {
			this.maxOptimisticValue = lf.valueAt(inputVec);
			this.maxError = (this.maxOptimisticValue-this.value)/this.maxOptimisticValue;
			if(maxError<0){
				this.maxError=1;//Outside the triangle (hyperpyramid) of
				// the search weights of the value vectors that generate this corner point
				// therefore we should find the appropriate triangle with
				// some other value vectors. This is however tedious, hence this hack,
				// i.e., it makes the algorithm slower. 
				//TODO: maybe fix later.
			}
				/*System.out.println("("+maxError+")");
				String result="Weight vector: (";
				DecimalFormat df = new DecimalFormat("#.##");
				for(int i=0; i<this.weightVector.length;i++){
					result+=df.format(this.weightVector[i]);
					if(i!=this.weightVector.length-1){
						result+=",";
					} else {
						result+=")";
					}
				}
				result+=" -> (";
						for(int i=0; i<inputVec.length;i++){
							result+=df.format(inputVec[i]);
							if(i!=inputVec.length-1){
								result+=",";
							} else {
								result+=")";
							}
						}
				System.out.println(result);
				
				result="Function: ";
				for(int i=0; i<lf.abcVector.length;i++){
					result+=df.format(lf.abcVector[i]);
					if(i!=lf.abcVector.length-1){
						result+=", ";
					} else {
						result+=" ";
					}
				}
				System.out.println(result);
				System.out.println(MathHelpers.matrixEquation2String(mat, vecVals));
			}*/
		}
		/*
		double[] vecVals = new double[this.valueVectors.size()];
		for(int i=0; i<this.valueVectors.size(); i++){
			double[] vec = this.valueVectors.get(i).getSearchWeights().get(this.searchWeightsIndicesVVs[i]);
			vecVals[i] = MathHelpers.innerProduct(vec,this.valueVectors.get(i).p.value);
			int correct =0; //TODO VANAF HIER
			for(int j=0; j<vec.length; j++){
				if(this.zeroWeights.contains(new Integer(j))){
					correct++;
				} else {
					mat[j-correct][i] = vec[j];
				}
			}
			solveVec[i]=this.weightVector[i];
		}
		//System.out.println(this.toString());
		//System.out.println(MathHelpers.matrixEquation2String(mat, solveVec));
		/*
		try{
			RealMatrix coefficients = new Array2DRowRealMatrix(mat,false);
			//coefficients.transpose();
			//if(++classCNT==3){
			DecompositionSolver solver = new LUDecomposition(coefficients).getSolver();			          
			RealVector constants = new ArrayRealVector(solveVec, false);
			RealVector solution = solver.solve(constants);
			double[] arr=solution.toArray();
//			System.out.print(":::: ");
//			for(int i=0; i<arr.length; i++){
//				System.out.print(arr[i]+" : ");
//			}
//			System.out.println();
			
			this.maxOptimisticValue = MathHelpers.innerProduct(vecVals, arr);
			this.maxError = (this.maxOptimisticValue-this.value)/this.maxOptimisticValue;
		} catch(SingularMatrixException sme){
			//System.out.println("AAARG: "+sme.getMessage());
		}
		*/
		//System.out.println(":::: "+ this.value+","+this.maxOptimisticValue+">>"+this.maxError);
	}
	
	public void maxOptimisticAndErrorFromValueAtExtrema(double[] valueAtExtrema){
		//System.out.println(weightVector.length+"   "+"   check for null pointer");
		this.maxOptimisticValue = CentralStatics.innerProduct(this.weightVector, valueAtExtrema);
		this.maxError = (this.maxOptimisticValue-this.value)/this.maxOptimisticValue;
	}
	
	public boolean equals(CornerPoint<T> cp){
		return CentralStatics.equalVectors(cp.weightVector, this.weightVector);
	}
	

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object o){
		try{
			CornerPoint<T> cp = ((CornerPoint<T>) o);
			return this.equals(cp);
		} catch (Exception e){
			return false;
		}
	}
	
	public boolean beatenBy(ValueAtBeliefVector<T> p, double precisionE){
		return (CentralStatics.innerProduct(this.weightVector, p.getValue())-this.value>precisionE);
	}
}
